/* The ns package is intended provides the net-score.org functionality.  */
package ns

/*
 */

import ( // for docs http://golang.org/pkg/ pkgname
	"appengine"
	"appengine/datastore"
	"appengine/user"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"time"
)

const (
	hostErr      = "datastore: cannot load field \"Host\" into a \"ns.DevSiteData\": no such struct field"
	oldDevKeyErr = "datastore: cannot load field \"OldDevKey\" into a \"ns.DevSiteData\": no such struct field"
)

var developerConfig = map[string]string{
	"trackSetDevKey":          "'ns'",
	"developerSetData":        "{}",
	"developerSetAddPath":     "'/user/developerAddSite/'",
	"developerSetRmPath":      "'/user/developerRmSite/'",
	"developerSetResultsPath": "'/user/developerResults/'",
}

func init() {
	http.HandleFunc("/user/developer/", developer)
	http.HandleFunc("/user/developerAddSite/", developerAddSite)
	http.HandleFunc("/user/developerRmSite/", developerRmSite)
}

type DevData struct {
	OwnerId   string
	SiteCount int
	SiteList  []int
}

type DevSiteData struct {
	DevKey        string
	URL           string
	RequestCount  int
	TestCount     int
	DateFirstTest int64
	DateLastTest  int64
}

type SiteData struct {
	DevKey        string
	OldDevKey     string
	Host          string
	URL           string
	RequestCount  int
	TestCount     int
	DateFirstTest int64
	DateLastTest  int64
}

func (dd *DevData) SiteListJson(c appengine.Context) ([]byte, error) {
	var JSONdata []byte
	var err error

	count := 0
	for count < len(dd.SiteList) {
		if dd.SiteList[count] != 0 {
			count++
		} else {
			break
		}
	}
	//Create the list of sites
	sl := make([]*DevSiteData, count)
	for i := 0; i < count; i++ {
		if dd.SiteList[i] != 0 {
			sl[i] = new(DevSiteData)
			k := fmt.Sprintf("NS-%s-%d", dd.OwnerId, dd.SiteList[i])
			key := datastore.NewKey(c, "SiteData", k, 0, nil)
			if err = datastore.Get(c, key, sl[i]); err != nil {
				//TODO(gavaletz) get rid of this brutal hack!
				if err.Error() != hostErr && err.Error() != oldDevKeyErr {
					return JSONdata, err
				}
			}
		} else {
			break
		}
	}
	if JSONdata, err = json.Marshal(sl); err != nil {
		return JSONdata, err
	}
	return JSONdata, nil
}

func (sd *SiteData) Delete(c appengine.Context) {
	key := datastore.NewKey(c, "SiteData", sd.DevKey, 0, nil)
	if err := datastore.Delete(c, key); err != nil {
		c.Errorf("Save:datastore.Delete err = %v", err)
	}
}

func (sd *SiteData) Save(c appengine.Context) {
	key := datastore.NewKey(c, "SiteData", sd.DevKey, 0, nil)
	if _, err := datastore.Put(c, key, sd); err != nil {
		c.Errorf("Save:datastore.Put err = %v", err)
	}
}

func (dd *DevData) Save(c appengine.Context) {
	key := datastore.NewKey(c, "DevData", dd.OwnerId, 0, nil)
	if _, err := datastore.Put(c, key, dd); err != nil {
		c.Errorf("Save:datastore.Put err = %v", err)
	}
}

func CheckSiteKey(u *user.User, c appengine.Context, siteKey string) bool {
	var err error

	//Admins can do this kind of thing
	if user.IsAdmin(c) {
		return true
	}

	//Check parts
	siteKeyParts := strings.Split(siteKey, "-")
	if len(siteKeyParts) != 3 {
		return false
	}

	//Check u.ID
	if u.ID != siteKeyParts[1] {
		return false
	}

	//Search for existing
	dd := new(DevData)
	key := datastore.NewKey(c, "DevData", u.ID, 0, nil)
	if err = datastore.Get(c, key, dd); err != nil {
		return false
	}
	var siteNumber int
	if siteNumber, err = strconv.Atoi(siteKeyParts[2]); err != nil {
		return false
	}
	var goodKey string
	if goodKey, err = dd.GetSiteKey(siteNumber); err != nil {
		return false
	}
	if goodKey == siteKey {
		return true
	}
	return false
}

func GetDevData(u *user.User, c appengine.Context) (*DevData, error) {
	//Search for existing
	dd := new(DevData)
	key := datastore.NewKey(c, "DevData", u.ID, 0, nil)
	if err := datastore.Get(c, key, dd); err == nil {
		return dd, nil
	}

	//Create New
	dd.OwnerId = u.ID
	dd.SiteCount = 0
	dd.SiteList = make([]int, 3)

	//Write New
	if _, err := datastore.Put(c, key, dd); err != nil {
		c.Errorf("GetDevData:datastore.Put (%s) err = %v", u, err)
		return dd, err
	}

	return dd, nil
}

func GetMasterDevData(c appengine.Context) (*DevData, error) {
	//Search for existing
	dd := new(DevData)
	key := datastore.NewKey(c, "DevData", "ns", 0, nil)
	if err := datastore.Get(c, key, dd); err == nil {
		return dd, nil
	}

	//Create New
	dd.OwnerId = "ns"
	dd.SiteCount = 0
	dd.SiteList = make([]int, 3)

	//Write New
	if _, err := datastore.Put(c, key, dd); err != nil {
		c.Errorf("GetMasterDevData:datastore.Put err = %v", err)
		return dd, err
	}

	return dd, nil
}

func GetSiteData(k string, c appengine.Context) (*SiteData, error) {
	sd := new(SiteData)
	key := datastore.NewKey(c, "SiteData", k, 0, nil)
	if err := datastore.Get(c, key, sd); err != nil {
		return sd, err
	}
	return sd, nil
}

func (dd *DevData) AddSiteKey(hostURL string, c appengine.Context) (string, error) {
	//create new key
	dd.SiteCount++
	k := fmt.Sprintf("NS-%s-%d", dd.OwnerId, dd.SiteCount)

	//add to SiteList
	if dd.SiteList[len(dd.SiteList)-1] != 0 {
		//The list was too short
		tmp := make([]int, len(dd.SiteList)+3)
		for i := 0; i < len(dd.SiteList); i++ {
			tmp[i] = dd.SiteList[i]
		}
		tmp[len(dd.SiteList)] = dd.SiteCount
		dd.SiteList = tmp
	} else {
		//The list was OK
		for i := 0; i < len(dd.SiteList); i++ {
			if dd.SiteList[i] == 0 {
				dd.SiteList[i] = dd.SiteCount
				break
			}
		}
	}

	var err error
	var siteURL *url.URL
	if siteURL, err = url.Parse(hostURL); err != nil {
		return k, err
	}
	c.Infof("%s", siteURL)

	if dd.OwnerId != "ns" {
		//Create site data
		sd := SiteData{
			DevKey:        k,
			Host:          siteURL.Host,
			URL:           siteURL.String(),
			RequestCount:  0,
			TestCount:     0,
			DateFirstTest: 0,
			DateLastTest:  0,
		}
		//store site data
		sd.Save(c)
	}

	//update DevData in datastore
	dd.Save(c)

	//return dev key
	return k, nil
}

func (dd *DevData) GetSiteKey(n int) (string, error) {
	if n > dd.SiteCount {
		return "", errors.New(fmt.Sprintf("Site number (%d) out of bounds (%d)", n, dd.SiteCount))
	}
	for i := 0; i < len(dd.SiteList); i++ {
		if dd.SiteList[i] == n {
			return fmt.Sprintf("NS-%s-%d", dd.OwnerId, n), nil
		}
	}
	return "", errors.New(fmt.Sprintf("Invalid site number (%d)", n))
}

func (dd *DevData) RmSiteKey(n int, c appengine.Context) error {
	var key string
	var err error
	if key, err = dd.GetSiteKey(n); err != nil {
		return err
	}
	//reasign the site data to ns
	var sd *SiteData
	var mdd *DevData
	if sd, err = GetSiteData(key, c); err != nil {
		return err
	}
	sd.Delete(c)
	if mdd, err = GetMasterDevData(c); err != nil {
		return err
	}
	sd.OldDevKey = sd.DevKey
	if sd.DevKey, err = mdd.AddSiteKey(sd.URL, c); err != nil {
		return err
	}

	//move all raw data from the old DevKey to the new one
	//TODO(gavaletz) #40 OldDevKey watch list
	q := datastore.NewQuery("TestRecord").Filter("DevKey =", sd.OldDevKey)
	var testRecordCount int
	if testRecordCount, err = q.Count(c); err != nil {
		c.Errorf("RmSiteKey:q.Count err = %v", err)
		return err
	}
	trs := make([]TestRecord, 0, testRecordCount)
	if _, err := q.GetAll(c, &trs); err != nil {
		c.Warningf("RmSiteKey:q.GetAll err = %v", err)
		return err
	}
	for i := range trs {
		trs[i].DevKey = sd.DevKey
		_, err = trs[i].Save(c)
		if err != nil {
			c.Errorf("RmSiteKey:trs[i].Save(c) err = %v", err)
		}
	}
	sd.Save(c)

	//rm from SiteList and shift zeros to the end
	for i := 0; i < len(dd.SiteList); i = i + 1 {
		if dd.SiteList[i] == n {
			dd.SiteList[i] = 0
		}
		if dd.SiteList[i] == 0 {
			if i+1 < len(dd.SiteList) {
				dd.SiteList[i] = dd.SiteList[i+1]
				dd.SiteList[i+1] = 0
			}
		}
	}
	dd.Save(c)
	return nil
}

/*TODO(gavaletz) #40 Admin console
admin developer takes a developer id in the path or uses ns!
*/

/* The results method displays the results to a user.

Acess control for this page is controlled by the app.yaml file.
*/
func developer(w http.ResponseWriter, r *http.Request) {
	gt := time.Now().Unix()
	c := appengine.NewContext(r)
	//get the user if we have one.
	u := user.Current(c)
	if u == nil {
		c.Errorf("DEVELOPER unknown user for %s", r.RemoteAddr)
		http.Error(w, "DEVELOPER unknown user", http.StatusInternalServerError)
		return
	}
	// Get some data.
	var dd *DevData
	var err error
	if dd, err = GetDevData(u, c); err != nil {
		c.Errorf("developer:GetDevData for %s err = %v", r.RemoteAddr, err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	//Convert to JSON
	var JSONdata []byte
	if JSONdata, err = dd.SiteListJson(c); err != nil {
		c.Errorf("developer:dd.SiteListJson for %s err = %v", r.RemoteAddr, err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	//Set the data
	var fc map[string]string
	if fc, err = newConfig(developerConfig, c, r, gt); err != nil {
		c.Errorf("RESULTSADMIN newConfig for %s err = %v", r.RemoteAddr, err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	fc["developerSetData"] = fmt.Sprintf("%s", JSONdata)

	h := HTMLHeader{
		Title: "net-score",
		ScriptList: []string{"https://www.google.com/jsapi",
			VersionFile("/", "strings-en-comp.js", c),
			VersionFile("/", "developer-comp.js", c)},
		StyleList: []string{VersionFile("/", "flock-comp.css", c)},
		JsConfig:  fc,
	}
	p := &HTMLPage{
		Head:   h,
		OnLoad: "developerStart();",
	}

	//return the page!
	if err = staticSet.ExecuteTemplate(w, "page", p); err != nil {
		c.Errorf("DEVELOPER staticSet.ExecuteTemplate for %s err = %v", r.RemoteAddr, err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
}

func developerRmSite(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)
	path := strings.Split(r.URL.Path, "/")
	if len(path) > 3 && path[3] != "" {
		var err error
		var n int
		n, err = strconv.Atoi(path[3])
		if err != nil {
			c.Errorf("developerRmSite:strconv.Atoi for %s err = %v", r.RemoteAddr, err)
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		//get the user if we have one.
		u := user.Current(c)
		if u == nil {
			c.Errorf("developerRmSite unknown user for %s", r.RemoteAddr)
			http.Error(w, "developerAddSite unknown user", http.StatusInternalServerError)
			return
		}
		// Get some data.
		var dd *DevData
		if dd, err = GetDevData(u, c); err != nil {
			c.Errorf("developerRmSite:GetDevData for %s err = %v", r.RemoteAddr, err)
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		if err = dd.RmSiteKey(n, c); err != nil {
			c.Errorf("developerRmSite:RmSiteKey for %s err = %v", r.RemoteAddr, err)
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		w.Header().Set("Content-Type", "text/plain; charset=utf-8")
		fmt.Fprintf(w, "%s", "OK")
	}
}

func developerAddSite(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)
	if r.Method != "POST" {
		c.Errorf("developerAddSite:r.Method != POST for %s", r.RemoteAddr)
		http.Error(w, "POST data only", http.StatusInternalServerError)
		return
	}

	if r.ContentLength == 0 {
		c.Errorf("developerAddSite:r.ContentLength == 0 for %s", r.RemoteAddr)
		http.Error(w, "POST empty", http.StatusInternalServerError)
		return
	}

	body := make([]byte, r.ContentLength)
	var bytesRead int
	var err error
	if bytesRead, err = io.ReadFull(r.Body, body); err != nil {
		c.Errorf("developerAddSite:io.ReadFull for %s err = %v", r.RemoteAddr, err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	if int64(bytesRead) != r.ContentLength {
		c.Warningf("developerAddSite:io.ReadFull for %s only read %d of %d bytes", r.RemoteAddr, bytesRead, r.ContentLength)
	}
	rawURL := string(body)

	//get the user if we have one.
	u := user.Current(c)
	if u == nil {
		c.Errorf("developerAddSite unknown user for %s", r.RemoteAddr)
		http.Error(w, "developerAddSite unknown user", http.StatusInternalServerError)
		return
	}
	// Get some data.
	var dd *DevData
	if dd, err = GetDevData(u, c); err != nil {
		c.Errorf("developerAddSite:GetDevData for %s err = %v", r.RemoteAddr, err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	var key string
	if key, err = dd.AddSiteKey(rawURL, c); err != nil {
		c.Errorf("developerAddSite:AddSiteKey for %s err = %v", r.RemoteAddr, err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	var sd *SiteData
	if sd, err = GetSiteData(key, c); err != nil {
		c.Errorf("developerAddSite:GetSiteData for %s err = %v", r.RemoteAddr, err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	var JSONdata []byte
	if JSONdata, err = json.Marshal(sd); err != nil {
		c.Errorf("developerAddSite:json.Marshal for %s err = %v", r.RemoteAddr, err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	fmt.Fprintf(w, "%s", JSONdata)
}
